package day32.Lambda;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Lambda表达式练习类
 * 
 * 这个类通过员工管理系统场景中的常见操作，
 * 来演示Java中Lambda表达式的实际应用。
 * 包括数据过滤、映射、排序、统计、分组等操作。
 * 这些操作在企业级应用中经常使用，
 * 掌握这些技能对Java开发者来说非常重要。
 * 
 * Lambda表达式是Java 8引入的一个重要特性，它可以简化匿名内部类的写法，
 * 使代码更加简洁易读。Lambda表达式通常与Stream API配合使用，
 * 用于处理集合数据。
 */

public class LambdaPractice {
    /**
     * 主方法 - 程序的入口点
     * 
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 创建员工列表用于演示
        // Arrays.asList()创建一个固定大小的列表，不能添加或删除元素，但可以修改元素
        List<Employee> employees = Arrays.asList(
            // 创建Employee对象，参数依次为：ID、姓名、薪资、部门、年龄
            new Employee(1, "Alice", 5000, "IT", 25),
            new Employee(2, "Bob", 6000, "HR", 30),
            new Employee(3, "Charlie", 7000, "IT", 35),
            new Employee(4, "Diana", 5500, "Finance", 28),
            new Employee(5, "Eve", 8000, "IT", 32),
            new Employee(6, "Frank", 4500, "HR", 27),
            new Employee(7, "Grace", 9000, "Finance", 38),
            new Employee(8, "Henry", 6500, "IT", 31)
        );

        System.out.println("=== Lambda表达式练习 ===\n");

        // 1. 打印所有员工信息
        System.out.println("1. 所有员工信息:");
        // forEach是一个终端操作，它会遍历流中的每个元素
        // System.out::println是方法引用，等价于e -> System.out.println(e)
        // 方法引用是Lambda表达式的一种简化写法，格式为 类名::方法名
        employees.forEach(System.out::println);

        // 2. 筛选出IT部门的员工
        System.out.println("\n2. IT部门员工:");
        // filter是一个中间操作，它会过滤出满足条件的元素
        employees.stream()  // 将集合转换为流，stream()方法返回一个顺序流
            .filter(e -> "IT".equals(e.getDepartment()))  // 过滤出IT部门的员工，filter接收一个Predicate函数接口
            .forEach(System.out::println);  // 输出结果，forEach是终端操作，执行完后流就关闭了

        // 3. 找出薪资大于6000的员工
        System.out.println("\n3. 薪资大于6000的员工:");
        // 使用filter查找薪资大于6000的员工
        employees.stream()
            .filter(e -> e.getSalary() > 6000)  // 薪资大于6000的条件，e是Lambda表达式的参数
            .forEach(System.out::println);      // 输出结果

        // 4. 将所有员工姓名转换为大写并收集到列表中
        System.out.println("\n4. 所有员工姓名(大写):");
        // map是一个中间操作，它会转换流中的每个元素
        List<String> upperNames = employees.stream()
            .map(Employee::getName)          // 提取员工姓名，Employee::getName是方法引用，等价于e -> e.getName()
            .map(String::toUpperCase)       // 将姓名转换为大写，String::toUpperCase等价于s -> s.toUpperCase()
            .collect(Collectors.toList());  // 将流收集到List中，collect是终端操作
        System.out.println(upperNames);

        // 5. 按薪资从高到低对员工进行排序
        System.out.println("\n5. 按薪资降序排列的员工:");
        // sorted是一个中间操作，它会对元素进行排序
        employees.stream()
            .sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()))  // 按薪资降序排序
            // Lambda表达式 (e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()) 定义了比较规则
            // e1和e2是两个要比较的Employee对象
            // Double.compare(e2.getSalary(), e1.getSalary()) 通过比较薪资实现降序排列
            .forEach(System.out::println);  // 输出结果

        // 6. 计算IT部门员工的平均薪资
        System.out.println("\n6. IT部门员工的平均薪资:");
        // mapToDouble将元素转换为double值，这是一个专门处理数值的流操作
        double avgSalaryIT = employees.stream()
            .filter(e -> "IT".equals(e.getDepartment()))  // 过滤出IT部门员工
            .mapToDouble(Employee::getSalary)             // 提取薪资并转换为double类型的流
            .average()                                    // 计算平均值，返回OptionalDouble类型
            // average()方法返回OptionalDouble是为了处理可能没有元素的情况
            .orElse(0.0);                                 // 如果没有元素，则返回默认值0.0
        System.out.println("IT部门平均薪资: " + avgSalaryIT);

        // 7. 找出年龄最大的员工
        System.out.println("\n7. 年龄最大的员工:");
        // max是一个终端操作，用于找出最大元素
        employees.stream()
            .max(Comparator.comparing(Employee::getAge))  // 使用Comparator.comparing按年龄比较找出最大值
            // Comparator.comparing(Employee::getAge) 创建一个按年龄比较的比较器
            .ifPresent(System.out::println);              // 如果存在最大值则打印，避免空指针异常
            // ifPresent方法是Optional类的方法，只有当Optional中有值时才执行参数中的操作

        // 8. 按部门对员工进行分组
        System.out.println("\n8. 按部门分组的员工:");
        // groupingBy是一个收集器，用于按指定条件对元素进行分组
        Map<String, List<Employee>> employeesByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));  // 按部门分组
            // groupingBy接收一个分类函数，这里使用方法引用Employee::getDepartment
            // 返回一个Map，key是部门名称，value是该部门的所有员工列表
        // forEach遍历Map中的每个条目，参数是BiConsumer函数接口
        employeesByDept.forEach((dept, empList) -> {
            // dept是部门名称，empList是该部门的员工列表
            System.out.println("部门: " + dept);
            empList.forEach(e -> System.out.println("  " + e));  // 输出该部门的所有员工，前面加两个空格表示缩进
        });

        // 9. 获取按字母顺序排序的员工姓名列表
        System.out.println("\n9. 按字母顺序排序的员工姓名:");
        // 组合使用map、sorted和collect操作
        List<String> sortedNames = employees.stream()
            .map(Employee::getName)         // 提取姓名
            .sorted()                       // 按字母顺序排序，默认是升序
            .collect(Collectors.toList());  // 收集到List中
        System.out.println(sortedNames);

        // 10. 检查是否有员工薪资超过8000
        System.out.println("\n10. 是否有员工薪资超过8000?:");
        // anyMatch是一个终端操作，用于检查是否有任何元素匹配条件
        boolean hasHighSalary = employees.stream()
            .anyMatch(e -> e.getSalary() > 8000);  // 检查薪资是否大于8000
            // anyMatch接收一个Predicate函数接口，只要有一个元素满足条件就返回true
        System.out.println("是否有薪资超过8000的员工: " + hasHighSalary);

        // 11. 统计每个部门的员工数量
        System.out.println("\n11. 各部门员工数量:");
        // 组合使用groupingBy和counting收集器
        Map<String, Long> countByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment,  // 按部门分组
                 Collectors.counting()));  // 统计每组中的元素数量
                 // groupingBy的第二个参数是下游收集器，用于对分组后的数据进一步处理
        countByDept.forEach((dept, count) -> 
            System.out.println(dept + "部门: " + count + "名员工"));

        // 12. 薪资最高的前3名员工
        System.out.println("\n12. 薪资最高的前3名员工:");
        // 组合使用sorted、limit和forEach操作
        employees.stream()
            .sorted(Comparator.comparing(Employee::getSalary).reversed())  // 按薪资降序排序
            // reversed()方法将比较器的结果反转，实现降序排列
            .limit(3)  // 限制结果为前3个元素，这是一个短路操作
            .forEach(System.out::println);  // 输出结果
    }
}

/**
 * 员工类，表示员工信息
 * 
 * 包含员工的基本属性：ID、姓名、薪资、部门和年龄
 * 这是一个简单的POJO类（Plain Old Java Object），用于存储数据
 */

class Employee {
    // 员工属性，使用private修饰符封装数据，符合面向对象编程的封装原则
    private int id;           // 员工ID，唯一标识一个员工
    private String name;      // 员工姓名，存储员工的姓名信息
    private double salary;    // 员工薪资，存储员工的薪资信息，使用double类型表示
    private String department; // 员工所属部门，存储员工所在的部门信息
    private int age;          // 员工年龄，存储员工的年龄信息

    /**
     * Employee类的构造方法
     * 
     * 构造方法用于创建Employee对象时初始化对象的属性
     * 
     * @param id         员工ID，用于唯一标识员工
     * @param name       员工姓名，员工的姓名信息
     * @param salary     员工薪资，员工的薪资数额
     * @param department 员工部门，员工所在的部门
     * @param age        员工年龄，员工的年龄
     */
    public Employee(int id, String name, double salary, String department, int age) {
        // 使用this关键字区分成员变量和参数变量
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.department = department;
        this.age = age;
    }

    /**
     * 获取员工ID
     * 
     * Getter方法用于获取私有属性的值
     * 
     * @return 员工ID，返回员工的唯一标识符
     */
    public int getId() { 
        return id; 
    }
    
    /**
     * 获取员工姓名
     * 
     * Getter方法用于获取私有属性的值
     * 
     * @return 员工姓名，返回员工的姓名信息
     */
    public String getName() { 
        return name; 
    }
    
    /**
     * 获取员工薪资
     * 
     * Getter方法用于获取私有属性的值
     * 
     * @return 员工薪资，返回员工的薪资数额
     */
    public double getSalary() { 
        return salary; 
    }
    
    /**
     * 获取员工部门
     * 
     * Getter方法用于获取私有属性的值
     * 
     * @return 员工部门，返回员工所在的部门信息
     */
    public String getDepartment() { 
        return department; 
    }
    
    /**
     * 获取员工年龄
     * 
     * Getter方法用于获取私有属性的值
     * 
     * @return 员工年龄，返回员工的年龄信息
     */
    public int getAge() { 
        return age; 
    }

    /**
     * 重写toString方法，提供Employee对象的字符串表示形式
     * 当我们打印Employee对象时，会调用这个方法
     * 
     * toString方法是Object类的方法，每个类都继承自Object类
     * 重写toString方法可以让对象以更友好的方式显示
     * 
     * @return Employee对象的字符串表示形式，包含所有属性信息
     */
    @Override
    public String toString() {
        // 返回格式化的字符串，包含员工的所有信息
        return "Employee{" +
                "id=" + id +                    // 员工ID
                ", name='" + name + '\'' +      // 员工姓名，使用单引号包围
                ", salary=" + salary +          // 员工薪资
                ", department='" + department + '\'' +  // 员工部门，使用单引号包围
                ", age=" + age +                // 员工年龄
                '}';
    }
}